package com.enation.app.javashop.core.promotion.seckill.service.impl;

import com.beust.jcommander.internal.Maps;
import com.enation.app.javashop.core.client.goods.GoodsClient;
import com.enation.app.javashop.core.goods.model.vo.CacheGoods;
import com.enation.app.javashop.core.goods.model.vo.GoodsSkuVO;
import com.enation.app.javashop.core.goods.service.GoodsManager;
import com.enation.app.javashop.core.promotion.PromotionErrorCode;
import com.enation.app.javashop.core.promotion.seckill.model.dos.SeckillApplyDO;
import com.enation.app.javashop.core.promotion.seckill.model.dos.SeckillDO;
import com.enation.app.javashop.core.promotion.seckill.model.dto.SeckillQueryParam;
import com.enation.app.javashop.core.promotion.seckill.model.enums.SeckillGoodsApplyStatusEnum;
import com.enation.app.javashop.core.promotion.seckill.model.enums.SeckillTypeEnum;
import com.enation.app.javashop.core.promotion.seckill.model.vo.SeckillApplyVO;
import com.enation.app.javashop.core.promotion.seckill.model.vo.SeckillGoodsVO;
import com.enation.app.javashop.core.promotion.seckill.model.vo.SeckillVO;
import com.enation.app.javashop.core.promotion.seckill.service.SeckillGoodsManager;
import com.enation.app.javashop.core.promotion.seckill.service.SeckillManager;
import com.enation.app.javashop.core.promotion.seckill.service.SeckillPromotionManager;
import com.enation.app.javashop.core.promotion.tool.model.dto.PromotionDTO;
import com.enation.app.javashop.core.promotion.tool.service.impl.AbstractPromotionRuleManagerImpl;
import com.enation.app.javashop.core.promotion.tool.support.PromotionCacheKeys;
import com.enation.app.javashop.core.shop.service.ShopManager;
import com.enation.app.javashop.core.trade.cart.service.CartOriginDataManager;
import com.enation.app.javashop.framework.cache.Cache;
import com.enation.app.javashop.framework.context.UserContext;
import com.enation.app.javashop.framework.database.DaoSupport;
import com.enation.app.javashop.framework.database.Page;
import com.enation.app.javashop.framework.exception.ServiceException;
import com.enation.app.javashop.framework.util.DateUtil;
import com.enation.app.javashop.framework.util.SqlUtil;
import com.enation.app.javashop.framework.util.StringUtil;
import io.swagger.models.auth.In;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.ZoneOffset;
import java.util.*;
import java.util.concurrent.locks.Lock;
import java.util.stream.Collectors;

/**
 * 限时抢购申请业务类
 *
 * @author Snow
 * @version v7.0.0
 * @since v7.0.0
 * 2018-04-02 17:30:09
 */
@Service
public class SeckillGoodsManagerImpl extends AbstractPromotionRuleManagerImpl implements SeckillGoodsManager {

    @Autowired
    @Qualifier("tradeDaoSupport")
    private DaoSupport daoSupport;

    @Autowired
    private GoodsClient goodsClient;

    @Autowired
    private SeckillManager seckillManager;

    @Autowired
    private SeckillPromotionManager seckillPromotionManager;

    @Autowired
    private Cache cache;

    @Autowired
    private RedissonClient redisson;

    @Autowired
    private GoodsManager goodsManager;
    @Autowired
    private CartOriginDataManager cartOriginDataManager;

    private final Logger logger = LoggerFactory.getLogger(getClass());

    @Override
    public Page list(SeckillQueryParam queryParam) {
        List param = new ArrayList();

        StringBuffer sql = new StringBuffer();
        sql.append("select * from es_seckill_apply where seckill_id=? ");
        param.add(queryParam.getSeckillId());

        if (UserContext.getSeller() != null) {
            sql.append(" and seller_id = ? ");
            param.add(UserContext.getSeller().getSellerId());
        }

        if (queryParam.getStatus() != null) {
            sql.append(" and status =? ");
            param.add(queryParam.getStatus());
        }

        if (queryParam.getTimeLine() != null) {
            sql.append(" and time_line =? ");
            param.add(queryParam.getTimeLine());
        }
        if (queryParam.getStartDay() != 0) {
            sql.append(" and start_day =? ");
            param.add(queryParam.getStartDay());
        }
        sql.append(" order by time_line");
        Page webPage = this.daoSupport.queryForPage(sql.toString(), queryParam.getPageNo(), queryParam.getPageSize(), SeckillApplyVO.class, param.toArray());

        return webPage;
    }


    @Override
    public void delete(Integer id) {

        this.daoSupport.delete(SeckillApplyDO.class, id);
    }

    @Override
    public SeckillApplyDO getModel(Integer id) {

        return this.daoSupport.queryForObject(SeckillApplyDO.class, id);
    }


    @Override
    @Transactional(value = "tradeTransactionManager",propagation = Propagation.REQUIRED, rollbackFor = {RuntimeException.class, ServiceException.class})
    public void addOrUpdateApply(List<SeckillApplyDO> list) {
        Integer sellerId = list.get(0).getSellerId();
        String sellerName = list.get(0).getShopName();
        Integer seckillId = list.get(0).getSeckillId();

        SeckillVO seckillVO = this.seckillManager.getModel(seckillId);

        List<SeckillApplyDO> oldSeckillGoods = getSeckillApplyBySeckillId(seckillVO.getSeckillId());
        List<Integer> oldSeckillGoodsIds=oldSeckillGoods.stream().filter(seckillApplyDO -> seckillApplyDO.getApplyId()!=null).map(SeckillApplyDO::getApplyId).collect(Collectors.toList());
        List<Integer> updateSeckillGoodsIds=list.stream().filter(seckillApplyDO -> seckillApplyDO.getApplyId()!=null).map(SeckillApplyDO::getApplyId).collect(Collectors.toList());

        oldSeckillGoodsIds.removeAll(updateSeckillGoodsIds);

        // 【1】删除秒杀商品
        if(!CollectionUtils.isEmpty(oldSeckillGoodsIds)){
            String str= StringUtil.listToString(oldSeckillGoodsIds,",");
            String sql = "delete from es_seckill_apply where apply_id in ("+str+")";
            this.daoSupport.execute(sql);
        }

        List<SeckillApplyDO> updateSeckillGoods=new ArrayList<>();
        List<SeckillApplyDO> newSeckillGoods=new ArrayList<>();
        List<Integer> newGoodsIds=new ArrayList<>();

        //如果是空的则不需要进行判断重复参与
        for (SeckillApplyDO seckillApplyDO : list) {
            // 【2】更新秒杀商品
            if (seckillApplyDO.getApplyId() != null) {
                StringBuffer sql = new StringBuffer("update es_seckill_apply set price=?,sold_quantity=?");
                List<Object> term = new ArrayList<>();
                term.add(seckillApplyDO.getPrice());
                term.add(seckillApplyDO.getSoldQuantity());
                if (seckillApplyDO.getLimitNum() != null) {
                    sql.append(" ,limit_num=? ");
                    term.add(seckillApplyDO.getLimitNum());
                }
                sql.append("  where apply_id=? ");
                term.add(seckillApplyDO.getApplyId());
                this.daoSupport.execute(sql.toString(), term.toArray());
                updateSeckillGoods.add(seckillApplyDO);
            } else {
                // 【3】新增秒杀

                Integer goodsId = seckillApplyDO.getGoodsId();
                //查询商品
                CacheGoods goods = goodsClient.getFromCache(goodsId);
                //判断参加活动的数量和库存数量
                if (seckillApplyDO.getSoldQuantity() > goods.getEnableQuantity()) {
                    throw new ServiceException(PromotionErrorCode.E402.code(), seckillApplyDO.getGoodsName() + ",此商品库存不足");
                }

                //商品的原始价格
                seckillApplyDO.setOriginalPrice(goods.getPrice());
                seckillApplyDO.setSellerId(sellerId);
                seckillApplyDO.setShopName(sellerName);
                seckillApplyDO.setStatus(SeckillGoodsApplyStatusEnum.PASS.name());
                seckillApplyDO.setSalesNum(0);
                this.daoSupport.insert(seckillApplyDO);

                int applyId = this.daoSupport.getLastId("es_seckill_apply");
                seckillApplyDO.setApplyId(applyId);

                newSeckillGoods.add(seckillApplyDO);
            }
        }

        // 批量删除促销信息
        seckillPromotionManager.batchDeletePromotionGoods(oldSeckillGoodsIds);
        // 批量更新促销信息
        seckillPromotionManager.batchUpdatePromotionGoods(updateSeckillGoods);
        //  批量添加促销信息
        seckillPromotionManager.batchAddPromotionGoods(newSeckillGoods);
        // 上架商品
        goodsManager.ups(sellerId,newGoodsIds.toArray(new Integer[newGoodsIds.size()]));

        if(seckillVO.getSeckillType().equals(SeckillTypeEnum.PLATFORM.value())){
            this.seckillManager.sellerApply(sellerId, seckillVO.getSeckillId());
        }

        this.cache.remove(getRedisKey(seckillVO.getStartDay(),seckillId));
    }



    /**
     * 回滚秒杀库存
     * @param goodsList
     */
    private void innerRollbackStock(List<SeckillGoodsVO> goodsList) {
        for (SeckillGoodsVO goodsVO : goodsList) {
            int num = goodsVO.getSoldNum();
            String sql = "update es_seckill_apply set sales_num = sales_num - ? where sku_id = ? and apply_id=?";
            this.daoSupport.execute(sql, num,  goodsVO.getSkuId(), goodsVO.getApplyId());
        }

    }



    @Override
    @Transactional(value = "tradeTransactionManager", propagation = Propagation.REQUIRED, rollbackFor = {RuntimeException.class, ServiceException.class})
    public void rollbackStock(List<PromotionDTO> promotionDTOList) {

        List<SeckillGoodsVO> lockedList = new ArrayList<>();

        //遍历活动与商品关系的类
        for (PromotionDTO promotionDTO : promotionDTOList) {
            SeckillGoodsVO goodsVO = new SeckillGoodsVO();
            //用户购买的数量
            int num = promotionDTO.getNum();
            goodsVO.setSoldNum(num);
            goodsVO.setSkuId(promotionDTO.getProductId());
            goodsVO.setApplyId(promotionDTO.getActId());
            goodsVO.setGoodsId(promotionDTO.getGoodsId());
            lockedList.add(goodsVO);

        }
        if (CollectionUtils.isEmpty(lockedList)) {
            return;
        }

        innerRollbackStock(lockedList);

        // 更新缓存
        this.cache.remove(getRedisKey(getSeckillIdByApplyId(promotionDTOList.get(0).getActId())));

    }

    @Override
    @Transactional(value = "tradeTransactionManager", propagation = Propagation.REQUIRED, rollbackFor = {RuntimeException.class, ServiceException.class})
    public void deleteSeckillGoods(Integer goodsId) {

        //删除限时抢购已经开始和未开始的商品
        this.daoSupport.execute("delete from es_seckill_apply where goods_id = ? and start_day >= ? ",goodsId,DateUtil.startOfTodDay());

        //移除缓存中的数据
        this.cache.remove(getRedisKey(null));

    }


    @Override
    public List<SeckillApplyDO> getSeckillApplyBySeckillId(Integer seckillId) {
        String sql = "select * from es_seckill_apply where seckill_id = ?  order by time_line";
        return this.daoSupport.queryForList(sql,SeckillApplyDO.class, seckillId);
    }

    @Override
    public SeckillGoodsVO getSeckillById(Integer applyId) {
        // TODO 设置缓存
        SeckillApplyDO applyDO = this.getModel(applyId);
        return transSeckillApplyDOToSeckillGoodsVO(applyDO);
    }

    @Override
    public SeckillGoodsVO getSeckillApplyBySkuId(Integer skuId) {
        // TODO 设置缓存
        long today = DateUtil.startOfTodDay();
        String sql = "select seckill_id from es_seckill_apply where sku_id = ? and start_day=? order by time_line";
        SeckillApplyDO seckillApplyDO = this.daoSupport.queryForObject(sql, SeckillApplyDO.class, skuId, today);
        return transSeckillApplyDOToSeckillGoodsVO(seckillApplyDO);
    }

    private Integer  getSeckillIdByApplyId(Integer applyId) {
        String sql = "select seckill_id from es_seckill_apply where apply_id = ?  order by time_line";
        return this.daoSupport.queryForInt(sql,applyId);
    }




    @Override
    @Transactional(value = "tradeTransactionManager", propagation = Propagation.REQUIRED, rollbackFor = {RuntimeException.class, ServiceException.class})
    public boolean addSoldNum(List<PromotionDTO> promotionDTOList) {

        List<SeckillGoodsVO> lockedList = new ArrayList<>();

        boolean result = true;
        //遍历活动与商品关系的类
        for (PromotionDTO promotionDTO : promotionDTOList) {

            try {

                //用户购买的数量
                int num = promotionDTO.getNum();

                String sql = "update es_seckill_apply set sales_num = sales_num +? where sku_id = ? and apply_id=? and (sold_quantity-sales_num)>=?";
                logger.debug("num is " + num + ";goodsid: " + promotionDTO.getGoodsId() + " ; applyId: " + promotionDTO.getActId());
                int rowNum = this.daoSupport.execute(sql, num, promotionDTO.getProductId(), promotionDTO.getActId(),num);

                //库存不足
                if (rowNum <= 0) {
                    logger.debug("秒杀更新失败");
                    result = false;
                    break;

                } else {
                    result = true;
                    SeckillGoodsVO goodsVO = new SeckillGoodsVO();
                    goodsVO.setSoldNum(num);
                    goodsVO.setSkuId(promotionDTO.getProductId());
                    goodsVO.setApplyId(promotionDTO.getActId());
                    goodsVO.setGoodsId(promotionDTO.getGoodsId());

                    //记录此商品已经锁成功,以便回滚
                    lockedList.add(goodsVO);
                    logger.debug("秒杀更新成功");

                }

                //发生锁库失败，则break;
                if (!result) {
                    break;
                }
                this.cache.remove(getRedisKey(getSeckillIdByApplyId(promotionDTOList.get(0).getActId())));

            } catch (Exception e) {
                e.printStackTrace();
                result = false;
            }
        }

        //如果有锁库失败，则回滚已经更新成功的
        if (!result) {
            innerRollbackStock(lockedList);
        }
        return result;
    }


    /**
     * @return
     */
    @Override
    public Map<Integer, List<SeckillGoodsVO>> getSeckillGoodsList(Long startDay,Integer seckillId) {

        //读取今天的时间
        startDay =startDay==null? DateUtil.startOfTodDay():startDay;
        //从缓存读取限时抢购的活动的商品
        String redisKey = getRedisKey(startDay,seckillId);
        Map<Integer, List<SeckillGoodsVO>> map =Maps.newHashMap();
        //Map<Integer, List<SeckillGoodsVO>> map = this.cache.getHash(redisKey);

        //如果redis中没有则从数据取
        if (map == null || map.isEmpty()) {

            //读取当天正在进行的活限时抢购活动的商品
            String sql = "select * from es_seckill_apply where  status = ? and seckill_id=?";
            List<SeckillApplyDO> list = this.daoSupport.queryForList(sql, SeckillApplyDO.class, SeckillGoodsApplyStatusEnum.PASS.name(),seckillId);

            //遍历所有的商品，并保存所有不同的时刻
            for (SeckillApplyDO applyDO : list) {
                map.put(applyDO.getTimeLine(), new ArrayList());
            }

            //遍历所有的时刻，并为每个时刻赋值商品
            for (SeckillApplyDO applyDO : list) {
                for (Map.Entry<Integer, List<SeckillGoodsVO>> entry : map.entrySet()) {
                    if (applyDO.getTimeLine().equals(entry.getKey())) {
                        SeckillGoodsVO seckillGoods = transSeckillApplyDOToSeckillGoodsVO(applyDO);
                        if (entry.getValue() == null) {
                            entry.setValue(new ArrayList<>());
                        }
                        entry.getValue().add(seckillGoods);
                    }
                }
            }

            //压入缓存
            for (Map.Entry<Integer, List<SeckillGoodsVO>> entry : map.entrySet()) {
                this.cache.putHash(redisKey, entry.getKey(), entry.getValue());
            }
        }

        return map;
    }



    @Override
    public List getSeckillGoodsByRangeTime(Integer rangeTime,String city) {
        //读取今天的时间
        long startDay =DateUtil.startOfTodDay();

        SeckillDO seckillDO = seckillManager.getSeckillByCity(city, startDay);

        if (seckillDO == null) {
            return null;
        }
        //读取当天正在进行的活限时抢购活动的商品
        String sql2 = "select * from es_seckill_apply where  status = ? and seckill_id=? and start_day=? and time_line=?";
        List<SeckillApplyDO> list = this.daoSupport.queryForList(sql2, SeckillApplyDO.class, SeckillGoodsApplyStatusEnum.PASS.name(),seckillDO.getSeckillId(),startDay,rangeTime);


        Map<Integer, Integer> cartNumMap = cartOriginDataManager.getUserCart();

        List<SeckillGoodsVO> seckillGoodsList = new ArrayList<>();

        Integer isStart=1;
        long startTime = LocalDateTime.of(LocalDate.now(), LocalTime.of(rangeTime, 0)).toInstant(ZoneOffset.of("+8")).toEpochMilli() / 1000;
        long currTime = DateUtil.getDateline();

        Long distanceStartTime=null;
        if (currTime < startTime) {
            distanceStartTime = startTime - currTime;
            isStart = 0;
        }
        //遍历所有的时刻，并为每个时刻赋值商品
        for (SeckillApplyDO applyDO : list) {
            SeckillGoodsVO seckillGoodsVO = transSeckillApplyDOToSeckillGoodsVO(applyDO);
            // 购物车社区团商品数量
            Integer cartNum = cartNumMap.get(seckillGoodsVO.getSkuId());
            seckillGoodsVO.setCartNum(cartNum == null ? 0 : cartNum);
            seckillGoodsVO.setIsStart(isStart);
            seckillGoodsVO.setDistanceStartTime(distanceStartTime);
            // 秒杀商品是否可售
            seckillGoodsVO.setSalesEnable(seckillGoodsVO.getRemainQuantity() > 0);

            seckillGoodsList.add(seckillGoodsVO);
        }

        return seckillGoodsList;
    }

    private SeckillGoodsVO transSeckillApplyDOToSeckillGoodsVO(SeckillApplyDO applyDO) {
        //活动开始日期（天）的时间戳
        long startDay = applyDO.getStartDay();
        //形成 2018090910 这样的串
        String timeStr = DateUtil.toString(startDay, "yyyyMMdd") + applyDO.getTimeLine();
        //得到开始日期的时间戳
        long startTime = DateUtil.getDateline(timeStr, "yyyyMMddHH");

        //查询商品
        CacheGoods goods = goodsClient.getFromCache(applyDO.getGoodsId());

        // 剩余库存
        GoodsSkuVO skuVO = goodsClient.getSkuFromCache(applyDO.getSkuId());

        SeckillGoodsVO seckillGoods = new SeckillGoodsVO();
        seckillGoods.setApplyId(applyDO.getApplyId());
        seckillGoods.setGoodsId(goods.getGoodsId());
        seckillGoods.setSkuId(applyDO.getSkuId());
        seckillGoods.setGoodsName(goods.getGoodsName());
        seckillGoods.setOriginalPrice(goods.getPrice());
        seckillGoods.setSeckillPrice(applyDO.getPrice());
        seckillGoods.setPrice(applyDO.getPrice());
        seckillGoods.setGoodsImage(goods.getThumbnail());
        seckillGoods.setStartTime(startTime);
        seckillGoods.setSeckillId(applyDO.getSeckillId());
        seckillGoods.setLimitNum(applyDO.getLimitNum());
        seckillGoods.setSoldQuantity(applyDO.getSoldQuantity());
        seckillGoods.setIsStart(1);
        // 秒杀数量控制
        seckillGoods.setSoldNum(applyDO.getSalesNum());
        seckillGoods.setRemainQuantity(applyDO.getSoldQuantity() - applyDO.getSalesNum());
        if (skuVO.getEnableQuantity() < 1 || seckillGoods.getRemainQuantity() < 1 ) {
            seckillGoods.setSoldNum(seckillGoods.getSoldQuantity());
            seckillGoods.setRemainQuantity(0);
            seckillGoods.setSalesEnable(false);
        } else {
            seckillGoods.setSalesEnable(true);
        }
        return seckillGoods;
    }

    public static void main(String[] args) {
        //活动开始日期（天）的时间戳
        long startDay = DateUtil.getDateline("20180101000000", "yyyyMMddHHMMss");
        //形成 2018090910 这样的串
        String timeStr = DateUtil.toString(startDay, "yyyyMMdd") + 10;
        //得到开始日期的时间戳
        long startTime = DateUtil.getDateline(timeStr, "yyyyMMddHH");

        String str = DateUtil.toString(startTime, "yyyyMMddHH");
        System.out.println(timeStr);
        System.out.println(str);

    }


    @Override
    public void addRedis(Long startTime, Integer rangeTime, SeckillGoodsVO goodsVO) {
        //得到活动缓存的key
        String redisKey = getRedisKey(startTime,goodsVO.getSeckillId());
        //查询活动商品
        List<SeckillGoodsVO> list = (List<SeckillGoodsVO>) this.cache.getHash(redisKey, rangeTime);
        if (list == null) {
            list = new ArrayList<>();
        }
        list.add(goodsVO);

        //压入缓存
        this.cache.putHash(redisKey, rangeTime, list);
    }


    @Override
    public List getSeckillGoodsList(Integer rangeTime, Integer pageNo, Integer pageSize) {

        //读取限时抢购活动商品
        Map<Integer, List<SeckillGoodsVO>> map = this.getSeckillGoodsList(null,null);
        List<SeckillGoodsVO> totalList = new ArrayList();

        //遍历活动商品
        for (Map.Entry<Integer, List<SeckillGoodsVO>> entry : map.entrySet()) {
            if (rangeTime.intValue() == entry.getKey().intValue()) {
                totalList = entry.getValue();
                break;
            }
        }

        //redis不能分页 手动根据分页读取数据
        List<SeckillGoodsVO> list = new ArrayList<SeckillGoodsVO>();
        int currIdx = (pageNo > 1 ? (pageNo - 1) * pageSize : 0);
        for (int i = 0; i < pageSize && i < totalList.size() - currIdx; i++) {
            SeckillGoodsVO goods = totalList.get(currIdx + i);
            list.add(goods);
        }

        return list;
    }


    private Lock getGoodsQuantityLock(Integer gbId) {
        RLock lock = redisson.getLock("seckill_goods_quantity_lock_" + gbId);
        return lock;
    }


    /**
     * 获取限时抢购key
     * @return
     */
    private String getRedisKey(Integer seckillId) {
        return  getRedisKey(DateUtil.getDateline(),seckillId);
    }

    private String getRedisKey(long time, Integer seckillId) {
        seckillId = seckillId == null ? 0 : seckillId;
        return PromotionCacheKeys.getSeckillKey(DateUtil.toString(time, "yyyyMMdd") + "_" + seckillId);
    }


}
